﻿using System;
using System.ComponentModel;
using System.Xml.Serialization;

namespace Zive.Color {

    public enum RgbChannels { R, G, B }

    /// <summary>
    /// An implementation of the sRGB color space.
    /// </summary>
    public class RgbColor: Color<RgbColor, RgbChannels> {

        public RgbColor( ) { }

        public RgbColor( Illuminants illuminant ) {
            _illuminant = illuminant;
        }

        public RgbColor( double r, double g, double b ) {
            _r = r;
            _g = g;
            _b = b;
            _byteR = _ClampAndRound( _r );
            _byteG = _ClampAndRound( _g );
            _byteB = _ClampAndRound( _b );
        }

        public RgbColor( Illuminants illuminant, double r, double g, double b ) {
            _illuminant = illuminant;
            _r = r;
            _g = g;
            _b = b;
            _byteR = _ClampAndRound( _r );
            _byteG = _ClampAndRound( _g );
            _byteB = _ClampAndRound( _b );
        }

        public RgbColor( RgbColor other ) {
            _illuminant = other._illuminant;
            _r = other._r;
            _g = other._g;
            _b = other._b;
            _byteR = _ClampAndRound( _r );
            _byteG = _ClampAndRound( _g );
            _byteB = _ClampAndRound( _b );
        }

        public static implicit operator System.Windows.Media.Color( RgbColor color ) {
            return new System.Windows.Media.Color {
                R = color._byteR,
                G = color._byteG,
                B = color._byteB,
                A = 255
            };
        }

        public static explicit operator RgbColor( System.Windows.Media.Color color ) {
            return new RgbColor( color.R, color.G, color.B );
        }

        public override RgbColor Clone( ) {
            return new RgbColor( this );
        }

        private Illuminants _illuminant = Illuminants.D65;
        /// <summary>Gets or sets the CIE standard illuminant and thus the white point.</summary>
        public override Illuminants Illuminant {
            get { return _illuminant; }
            set {
                _illuminant = value;
                OnPropertyChanged( "Illuminant" );
            }
        }

        private double _r;
        /// <summary>Gets or sets the value of the sRGB R channel of the color.</summary>
        public double R {
            get { return _r; }
            set {
                _r = value;
                _byteR = _ClampAndRound( _r );
                OnPropertyChanged( "R" );
                OnPropertyChanged( "ByteR" );
            }
        }

        private double _g;
        /// <summary>Gets or sets the value of the sRGB G channel of the color.</summary>
        public double G {
            get { return _g; }
            set {
                _g = value;
                _byteG = _ClampAndRound( _g );
                OnPropertyChanged( "G" );
                OnPropertyChanged( "ByteG" );
            }
        }

        private double _b;
        /// <summary>Gets or sets the value of the sRGB B channel of the color.</summary>
        public double B {
            get { return _b; }
            set {
                _b = value;
                _byteB = _ClampAndRound( _b );
                OnPropertyChanged( "B" );
                OnPropertyChanged( "ByteB" );
            }
        }

        public override double this[ RgbChannels channel ] {
            get { return _getChannelFuncs[(int) channel]( this ); }
            set { _setChannelFuncs[(int) channel]( this, value ); }
        }

        private byte _byteR;
        /// <summary>Gets or sets the byte value of the sRGB R channel of the color.</summary>
        [XmlIgnore]
        public byte ByteR {
            get { return _byteR; }
            set {
                _r = value;
                _byteR = value;
                OnPropertyChanged( "R" );
                OnPropertyChanged( "ByteR" );
            }
        }

        private byte _byteG;
        /// <summary>Gets or sets the byte value of the sRGB G channel of the color.</summary>
        [XmlIgnore]
        public byte ByteG {
            get { return _byteG; }
            set {
                _g = value;
                _byteG = value;
                OnPropertyChanged( "G" );
                OnPropertyChanged( "ByteG" );
            }
        }

        private byte _byteB;
        /// <summary>Gets or sets the byte value of the sRGB B channel of the color.</summary>
        [XmlIgnore]
        public byte ByteB {
            get { return _byteB; }
            set {
                _b = value;
                _byteB = value;
                OnPropertyChanged( "B" );
                OnPropertyChanged( "ByteB" );
            }
        }

        public byte GetByteByChannel( RgbChannels channel ) {
            return _getByteChannelFuncs[ (int) channel ]( this );
        }

        //
        // Implementation
        //

        private static readonly Func<RgbColor, double>[] _getChannelFuncs = {
            color => color._r,
            color => color._g,
            color => color._b,
        };

        private static readonly Action<RgbColor, double>[ ] _setChannelFuncs = {
            ( color, value ) => { color.R = value; },
            ( color, value ) => { color.G = value; },
            ( color, value ) => { color.B = value; },
        };

        private static readonly Func<RgbColor, byte>[] _getByteChannelFuncs = {
            color => color._byteR,
            color => color._byteG,
            color => color._byteB,
        };

        private byte _ClampAndRound( double value ) {
            if ( value < 0d ) {
                return 0;
            }
            if ( value > 255d ) {
                return 255;
            }
            return (byte) Math.Round( value, 0, MidpointRounding.ToEven );
        }

        #region Object Members

        public override string ToString( ) {
            return string.Format( "R={0:N1} G={1:N1} B={2:N1} illuminant={3:G}", _r, _g, _b, _illuminant );
        }

        #endregion

        #region INotifyPropertyChanged Members

        public override event PropertyChangedEventHandler PropertyChanged;

        protected override void OnPropertyChanged( string propertyName ) {
            var handlers = PropertyChanged;
            if ( null != handlers ) {
                handlers( this, new PropertyChangedEventArgs( propertyName ) );
            }
        }

        #endregion

    }

}
